/* 
 * Copyright 2009 The Tapestry-GAE team.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.tapestry.gae.protobuf;

import java.util.Iterator;

import org.apache.commons.codec.binary.Base64;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.Builtin;
import org.apache.tapestry5.ioc.services.ClassNameLocator;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.services.ValueEncoderFactory;

import com.googlecode.tapestry.gae.model.PaginationList;
import com.googlecode.tapestry.gae.model.PaginationOptions;

/**
 * 配置<a href="http://code.google.com/p/protobuf/">Google protocol
 * buffer</a>的Value Encoder. 提供了通过Object<->String两者之间的转换,方便在查询的时候记录查询条件.
 * 
 * @author dong
 * @author <a href="jun.tsai@ganshane.net">Jun Tsai</a>
 * @version $Revision: 2956 $
 * @since 0.0.1
 */
public class ProtocolBuffersModule {

	public static void contributeValueEncoderSource(
			MappedConfiguration<Class, ValueEncoderFactory> configuration,
			final ClassNameLocator classNameLocator) {
		final ClassLoader contextClassLoader = Thread.currentThread()
				.getContextClassLoader();
		// add PaginationOptions class type coercer
		addProtobufferCoercer(configuration, contextClassLoader,
				PaginationOptions.class.getName());
	}

	// add PaginationList --> List type coercer
	public static void contributeTypeCoercer(
			Configuration<CoercionTuple> configuration,

			@Builtin final TypeCoercer coercer)

	{
		add(configuration, PaginationList.class, PaginationOptions.class,
				new Coercion<PaginationList, PaginationOptions>() {
					public PaginationOptions coerce(PaginationList input) {
						return input.options();
					}
				});
		add(configuration, Iterator.class, Iterable.class,
				new Coercion<Iterator, Iterable>() {
					public Iterable coerce(final Iterator input) {
						return new Iterable() {

							@Override
							public Iterator iterator() {
								return input;
							}
						};
					}
				});
		add(configuration, PaginationList.class, Iterable.class,
				new Coercion<PaginationList, Iterable>() {
					public Iterable coerce(PaginationList input) {
						return coercer.coerce(input.collectionObject(),
								Iterable.class);
					}
				});
	}

	private static <S, T> void add(Configuration<CoercionTuple> configuration,
			Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion) {
		CoercionTuple<S, T> tuple = new CoercionTuple<S, T>(sourceType,
				targetType, coercion);

		configuration.add(tuple);
	}

	private static void addProtobufferCoercer(
			MappedConfiguration<Class, ValueEncoderFactory> configuration,
			ClassLoader contextClassLoader, String className) {

		try {
			Class protoClass = contextClassLoader.loadClass(className);

			// 假如class是ProtocolBuffer的子类，则进行处理
			if (ProtocolBuffer.class.isAssignableFrom(protoClass)) {
				final ValueEncoderFactory factory = new ValueEncoderFactory() {
					@SuppressWarnings("unchecked")
					public ValueEncoder create(Class type) {
						return new ProtoValueEncoder(type);
					}
				};
				configuration.add(protoClass, factory);
			}
		} catch (ClassNotFoundException ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * 针对Protobuf类型的对象进行Encoder
	 * 
	 * @author dong
	 * @author <a href="jun.tsai@ganshane.net">Jun Tsai</a>
	 * @version $Revision: 2956 $
	 * @since 0.0.1
	 */
	public static class ProtoValueEncoder<T extends ProtocolBuffer> implements
			ValueEncoder<T> {
		final Coercion<T, String> proto2string = new Object2ProtoStringCoercion<T>();
		final Coercion<String, T> string2proto;

		public ProtoValueEncoder(Class<T> type) {
			string2proto = new ProtoString2ObjectCoercion<T>(type);
		}

		/**
		 * @see org.apache.tapestry5.ValueEncoder#toClient(java.lang.Object)
		 */
		@Override
		public String toClient(T value) {
			return proto2string.coerce(value);
		}

		/**
		 * @see org.apache.tapestry5.ValueEncoder#toValue(java.lang.String)
		 */
		@Override
		public T toValue(String clientValue) {
			return this.string2proto.coerce(clientValue);
		}
	}

	/**
	 * 从String 到Protobuffer
	 * 
	 * @author dong
	 * @author <a href="jun.tsai@ganshane.net">Jun Tsai</a>
	 * @version $Revision: 2956 $
	 * @since 0.0.1
	 */
	static class ProtoString2ObjectCoercion<T extends ProtocolBuffer>
			implements Coercion<String, T> {
		private final Class<T> type;

		public ProtoString2ObjectCoercion(Class<T> type) {
			this.type = type;
		}

		/**
		 * @see org.apache.tapestry5.ioc.services.Coercion#coerce(java.lang.Object)
		 */
		@Override
		public T coerce(String input) {
			if (input == null) {
				return null;
			}

			T obj = null;
			try {
				obj = this.type.newInstance();
				if ("X".equals(input)) {
					return obj;
				}
				// 先通过base64进行解码
				byte[] _b64 = Base64.decodeBase64(input.getBytes());
				// 通过给定的二进制码，然后来反序列化对象
				obj.mergeData(_b64);
				return obj;
			} catch (Throwable te) {
				throw new RuntimeException(te);
			}
		}

	}

	/**
	 * 从对象到Protobuf的string的转换
	 * 
	 * @author dong
	 * @author <a href="jun.tsai@ganshane.net">Jun Tsai</a>
	 * @version $Revision: 2956 $
	 * @since 0.0.1
	 */
	static class Object2ProtoStringCoercion<T extends ProtocolBuffer>
			implements Coercion<T, String> {
		/**
		 * @see org.apache.tapestry5.ioc.services.Coercion#coerce(java.lang.Object)
		 */
		@Override
		public String coerce(T input) {
			if (input == null) {
				return null;
			}
			String str = new String(Base64.encodeBase64(input.getData()));
			if (InternalUtils.isBlank(str)) {
				return "X";
			}
			return str;
		}
	}
}
